package com.mic.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mic.constant.MyAuthorizationGrantType;
import com.mic.exception.ServiceException;
import com.mic.util.AuthUtils;
import com.mic.util.SpringUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author cch
 * @date 2025年06月09日 8:56
 */
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    private static final String BASIC_ = "Basic ";
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private RegisteredClientRepository registeredClientRepository;
    @Autowired
    private OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
    @Autowired
    private OAuth2AuthorizationService authorizationService;
    @Autowired
    private HandlerExceptionResolver handlerExceptionResolver;

    private static final Logger LOGGER = LogManager.getLogger();
    private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);

        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)authentication;
        //解决直接抛出异常，RestControllerAdvice异常统一处理器无法拦截的问题
//        HandlerExceptionResolver handlerExceptionResolver = SpringUtils.getBean("handlerExceptionResolver");

        if (header == null || !header.startsWith(BASIC_)) {
            handlerExceptionResolver.resolveException(request, response, null,new ServiceException("请求头中client信息为空"));
            return;
        }

        try {
            String[] tokens = AuthUtils.extractAndDecodeHeader(header);
            assert tokens.length == 2;
            String clientId = tokens[0];

            RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);

            //校验secret
            if (!passwordEncoder.matches( tokens[1], registeredClient.getClientSecret())) {
                handlerExceptionResolver.resolveException(request, response, null,new ServiceException("Given client ID does not match authenticated client"));
                return;
            }
            //不强制校验scope，可以去掉
            String scope = request.getParameter("scope");
            if (scope == null) {
                handlerExceptionResolver.resolveException(request, response, null,new ServiceException("请求参数中无scope参数"));
                return;
            }
            Set<String> requestedScopes = new HashSet<>(Arrays.stream(scope.split(",")).collect(Collectors.toSet()));
            Set<String> authorizedScopes = registeredClient.getScopes();// Default to configured scopes
            if (!CollectionUtils.isEmpty(requestedScopes)) {
                Set<String> unauthorizedScopes = requestedScopes.stream()
                        .filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope))
                        .collect(Collectors.toSet());
                if (!CollectionUtils.isEmpty(unauthorizedScopes)) {
                    throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
                }

                authorizedScopes = new LinkedHashSet<>(requestedScopes);
            }

            // 3. 构建令牌上下文
            OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
                    .registeredClient(registeredClient)
                    .principal(authentication)
//                    .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                    .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                    .authorizedScopes(authorizedScopes)
                    .authorizationGrantType(MyAuthorizationGrantType.MOBILE) // 根据实际授权类型调整
                    .authorizationGrant(token)
                    .build();

            // 4. 生成访问令牌
            OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
            if (generatedAccessToken == null) {
                OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
                        "The token generator failed to generate the access token.", "");
                throw new OAuth2AuthenticationException(error);
            }

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Generated access token");
            }

            OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
                    generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
                    generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
            if (accessToken == null) {
                throw new IllegalStateException("Failed to generate access token");
            }
            //还可以新增refreshToken
            // 6. 构建并保存授权信息
            // @formatter:off
            OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
                    .principalName(authentication.getName())
                    .authorizationGrantType(MyAuthorizationGrantType.MOBILE)
                    .accessToken(accessToken)
                    .authorizedScopes(authorizedScopes)
                    .attribute(Principal.class.getName(), authentication);
            OAuth2Authorization authorization = authorizationBuilder.build();
            this.authorizationService.save(authorization);
            LOGGER.info("获取token 成功：{}", accessToken.getTokenType().getValue());

            OAuth2AccessTokenResponse.Builder builder =
                    OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
                            .tokenType(accessToken.getTokenType()).scopes(authorizedScopes);
            if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
                builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
            }
            OAuth2AccessTokenResponse accessTokenResponse = builder.build();

            Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
                    .convert(accessTokenResponse);

            response.setCharacterEncoding("UTF-8");
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            PrintWriter printWriter = response.getWriter();
            printWriter.append(objectMapper.writeValueAsString(tokenResponseParameters));
        } catch (IOException e) {
            throw new BadCredentialsException(
                    "Failed to decode basic authentication token");
        }
    }
}
